﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Reflection;
using tiDE.API.Utility;

namespace tiDE.API.Utility
{
    public class DataConverter
    {
        public static int GetAppPages(string AppFile)
        {
            Stream AppStream = new FileStream(AppFile, FileMode.Open);
            AppStream.Position = 160;
            string Length = ((char)AppStream.ReadByte()).ToString() +
                ((char)AppStream.ReadByte());
            AppStream.Close();
            return HexToByte(Length);
        }

        public static byte[] GetDataFromHexFile(Stream Hex)
        {
            StreamReader reader = new StreamReader(Hex);
            List<byte> BinList = new List<byte>();
            string hexFile = reader.ReadToEnd();
            hexFile = hexFile.Replace("\r", "");
            string[] lines = hexFile.Split(new char[] { '\n' });
            for (int i = 0; i < lines.Length - 2; i++)
            {
                string newLine = lines[i];
                newLine = newLine.Replace(":", "");
                newLine = newLine.Remove(newLine.Length - 2);
                newLine = newLine.Substring(8);
                BinList.AddRange(HexToBytes(newLine));
            }
            return BinList.ToArray();
        }

        public static void HexTo8xp(string File, string OutputFile, string onCalcName)
        {
            byte[] bin = GetDataFromHexFile(new StreamReader(File).BaseStream);
            Save8xp(true, bin, onCalcName, OutputFile);
        }

        internal static string LaunchEmbeddedProgram(string FileName, string args)
        {
            ProcessStartInfo info = new ProcessStartInfo();
            CopyFileFromAssembly(FileName, "TempFile.exe");
            info.UseShellExecute = false;
            info.WindowStyle = ProcessWindowStyle.Hidden | ProcessWindowStyle.Minimized;
            info.RedirectStandardOutput = true;
            info.RedirectStandardError = true;
            info.Arguments = args;
            info.CreateNoWindow = true;
            info.FileName = FileName;
            Process program = new Process();
            program.StartInfo = info;
            program.Start();
            string Output = program.StandardOutput.ReadToEnd();
            string Error = program.StandardError.ReadToEnd();
            program.WaitForExit();
            File.Delete("TempFile.exe");
            return Output + "\nErrors:\n" + Error;
        }

        internal static string LaunchProgram(string FileName, string args)
        {
            ProcessStartInfo info = new ProcessStartInfo();
            info.UseShellExecute = false;
            info.WindowStyle = ProcessWindowStyle.Hidden | ProcessWindowStyle.Minimized;
            info.RedirectStandardOutput = true;
            info.RedirectStandardError = true;
            info.Arguments = args;
            info.CreateNoWindow = true;
            info.FileName = FileName;
            Process program = new Process();
            program.StartInfo = info;
            program.Start();
            string Output = program.StandardOutput.ReadToEnd();
            string Error = program.StandardError.ReadToEnd();
            program.WaitForExit();
            return Output + "\nErrors:\n" + Error;
        }

        internal static void CopyFileFromAssembly(string AssemblyFile, string NewLocation)
        {
            Assembly thisExe = Assembly.GetExecutingAssembly();
            Stream stream = thisExe.GetManifestResourceStream("TI_Developer_API" + "." + AssemblyFile);
            Stream newFile = File.Create(NewLocation);
            byte[] buffer = new byte[stream.Length];
            stream.Read(buffer, 0, (int)stream.Length);
            newFile.Write(buffer, 0, (int)stream.Length);
            newFile.Close();
            stream.Close();
        }

        public static string GetDirectoryFromFile(string File)
        {
            string newfile = File.Remove(File.LastIndexOf("\\") + 1);
            return File.Remove(File.LastIndexOf("\\") + 1);
        }

        public static string HexTo8xk(bool SignAs84, string HexFile, string OutputFile)
        {
            string args = string.Format("-g -t 8xk -o {1} {0}", HexFile, OutputFile);
            string result = LaunchProgram("rabbitsign.exe", args);
            return result;
        }

        public static string StripDirectory(string File)
        {
            return File.Substring(File.LastIndexOf('\\') + 1);
        }

        public static string RomTo8xu(bool SignAs84, string RomFile, string OutputFile, string PageArguments,
            byte MajorVersion, byte MinorVersion)
        {
            //try
            //{
                string args = string.Format("-k{3} -m{4} -n{3} -h03 -f{2} -o{0} {1}"
                    , new object[] { StripDirectory(OutputFile), PageArguments, SignAs84 ? "0A.key" : "04.key",
                    SignAs84 ? "0A" : "04", ByteToHex(MajorVersion), ByteToHex(MinorVersion)});
                if (File.Exists(StripDirectory(RomFile)))
                    File.Delete(StripDirectory(RomFile));
                File.Copy(RomFile, StripDirectory(RomFile));
                string Build8XUFile = GetDirectoryFromFile(RomFile) + "\\Build8XU.exe";
                string result = LaunchProgram("Build8XU.exe", args);
                File.Delete(StripDirectory(RomFile));
                if (File.Exists(OutputFile))
                    File.Delete(OutputFile);
                File.Copy(StripDirectory(OutputFile), OutputFile);
                return result;
            //}
            //catch
            //{
            //    return "Internal Error.";
            //}
        }

        public static string HexToRom(CalculatorModel Model, string HexFile, string OutputFile)
        {
            string modelstring = "";
            switch (Model)
            {
                case CalculatorModel.TI83Plus:
                    modelstring = "83P";
                    break;
                case CalculatorModel.TI84Plus:
                    modelstring = "84P";
                    break;
                default:
                    return "Calculator model not supported.";
            }
            string args = string.Format("{0} {1} {2}", new object[] { modelstring, HexFile, OutputFile });
            string result = LaunchProgram("Hex2ROM.exe", args);
            return result;
        }

        static List<byte> AllBytes;
        static StreamWriter writer;

        /// <summary>
        /// Builds a *.bin file into a *.8xp file
        /// </summary>
        /// <param name="bin">The *.bin file</param>
        /// <param name="ProgName">The name of the program on-calc</param>
        /// <param name="output">The output *.8xp file</param>
        public static void ConvertBinTo8xp(string bin, string ProgName, string output)
        {
            StreamReader reader = new StreamReader(bin);
            byte[] binBytes = new byte[reader.BaseStream.Length];
            reader.BaseStream.Read(binBytes, 0, (int)reader.BaseStream.Length);
            reader.Close();
            Save8xp(true, binBytes, ProgName, output);
        }

        /// <summary>
        /// Saves a *.8xp program
        /// </summary>
        /// <param name="Protected">Determines whether the program will be "locked" on-calc</param>
        /// <param name="Data">The program data, in bytes</param>
        /// <param name="ProgName">The string representation of the name</param>
        /// <param name="File">The file to save to</param>
        public static void Save8xp(bool Protected, byte[] Data, string ProgName, string File)
        {
            // Instatiate the byte list that will be used to create the checksum
            List<byte> Data2 = new List<byte>(Data);
            Data2.Add(0x00);
            Data = Data2.ToArray(); // TODO: Fix the bug instead of working around it
            AllBytes = new List<byte>();
            int Length = Data.Length - 1;
            // Fix the name of the program
            ProgName.ToUpper();
            if (ProgName.Length > 8)
                ProgName.Remove(8);
            Console.WriteLine("Creating program " + ProgName);
            // Create the stream
            writer = new StreamWriter(File);
            // The header (something to the effect of **TI83F*, with a bunch of zeroes)
            byte[] header = HexToBytes("2a2a54493833462a000000");
            // Manually fix some of the header bytes (I can't remember the hex equivalents of these and am too lazy to fix it)
            header[8] = 26;
            header[9] = 10;
            // Write our comment (I think it's "Created by Create8X" followed by zeroes, I calculated it yesterday)
            byte[] comment = HexToBytes("4372656174656420627920436f6e76657274385800000000000000000000000000000000000000000000");
            Console.WriteLine("Begin write...");
            // Write the header and comment
            Write(header, 0, header.Length);
            Write(comment, 0, comment.Length);
            // Theres a whole bunch of weird stuff about the length, don't ask me why
            Write(BitConverter.GetBytes((Int16)(Length + 19)), 0, 2);
            // Weird stuff
            Write(new byte[] { 13, 0 }, 0, 2);
            Write(BitConverter.GetBytes((Int16)(Length + 2)), 0, 2);
            // Write the protected value (6 = protected)
            WriteByte((byte)(Protected ? 6 : 5));
            // The name
            Write(StringToBytes(ProgName), 0, ProgName.Length);
            // The name must take up 8 characters.  If it doesn't, we have to add zeroes
            if (ProgName.Length < 8)
                for (int i = 0; i < 8 - ProgName.Length; i++)
                    WriteByte(0);
            // More weird stuff
            Write(new byte[] { 0, 0 }, 0, 2);
            // More length stuff
            Write(BitConverter.GetBytes((Int16)(Length + 2)), 0, 2);
            Write(BitConverter.GetBytes((Int16)(Length)), 0, 2);
            // The actual length, finally
            Write(Data, 0, Length);
            // And the checksum (all of the bytes from 36 forward, added together and truncated for overflow)
            writer.BaseStream.Write(GetChecksum(), 0, GetChecksum().Length);
            // And save all that hard work.
            writer.Close();
            Console.WriteLine("Write successful.");
        }

        #region Helpers
        private static void Write(byte[] bytes, int startIndex, int Length)
        {
            AllBytes.AddRange(bytes);
            writer.BaseStream.Write(bytes, startIndex, Length);
        }

        private static void WriteByte(byte b)
        {
            AllBytes.Add(b);
            writer.BaseStream.WriteByte(b);
        }

        private static byte[] GetChecksum()
        {
            short chk = 0;
            unchecked
            {
                for (int i = 36; i < AllBytes.Count; i++)
                    chk += (short)AllBytes[i];
            }
            return BitConverter.GetBytes(chk);
        }

        public static byte HexToByte(string HexValue)
        {
            int num = 0;
            int.TryParse(HexValue, System.Globalization.NumberStyles.HexNumber, null, out num);
            return (byte)num;
        }

        private static byte[] StringToBytes(string Str)
        {
            byte[] result = new byte[Str.Length];
            int i = 0;
            foreach (char c in Str.ToCharArray())
            {
                result[i] = BitConverter.GetBytes(c)[0];
                i++;
            }
            return result;
        }

        public static byte[] HexToBytes(string Hex)
        {
            byte[] res = new byte[Hex.Length / 2];
            for (int i = 0; i < Hex.Length; i += 2)
            {
                res[i / 2] = HexToByte(Hex.Substring(i, 2));
            }
            return res;
        }

        public static string BytesToHex(byte[] Input)
        {
            string hex = "";
            foreach (byte c in Input)
                hex += ByteToHex(c);
            return hex;
        }

        public static string ByteToHex(byte Byte)
        {
            int first = (int)Byte / 16;
            int second = (int)Byte % 16;
            return "0123456789ABCDEF".Substring(first, 1) +
                "0123456789ABCDEF".Substring(second, 1);
        }
        #endregion
    }
}
